package com.demon.auto.scheduler;

import com.demon.auto.handle.SignWebSocketHandler;
import com.demon.auto.model.dto.ClientConfigDto;
import com.demon.auto.model.dto.SignClientDto;
import com.demon.auto.service.SignClientService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ThreadLocalRandom;

/**
 * 定时任务服务
 *
 * @author huangrz
 * @version 1.0
 * @date 2025/02/13 14:18
 */
@Slf4j
@Service
public class DynamicTaskScheduler {

    /**
     * 客户端定时任务集合 {clientName, {config, task}}
     */
    private final static Map<String, Map<String, Object>> CLIENT_SCHEDULED_TASK_LIST = new HashMap<>();

    @Autowired
    private TaskScheduler taskScheduler;

    @Autowired
    private SignClientService signClientService;

    @Resource
    private SignWebSocketHandler signWebSocketHandler;

    @PostConstruct
    public void init() {
        scheduleDailyTask();
    }

    /**
     * 每天凌晨3点重新调度任务
     */
    @Scheduled(cron = "0 0 3 * * ?")
    public void scheduleDailyTask() {
        signWebSocketHandler.clearSignProcessInfo();
        List<SignClientDto> allOpenSignClientList = signClientService.getAllOpenSignClientList();
        if (CollectionUtils.isEmpty(allOpenSignClientList)) {
            return;
        }
        for (SignClientDto signClientDto : allOpenSignClientList) {
            try {
                buildClientScheduleTask(signClientDto);
            } catch (Exception e) {
                log.error("构建客户端定时执行任务异常！clientInfo:{}", signClientDto.toString(), e);
            }
        }
    }

    /**
     * 构建客户端定时执行任务
     */
    public void buildClientScheduleTask(SignClientDto client) {
        String clientName = client.getClientName();
        ClientConfigDto config = client.getConfig();
        if (config == null) {
            log.error("[{} 客户端定时任务] 客户端配置为空！", clientName);
            return;
        }

        // 取消当前任务
        cancelClientTask(clientName);

        // 判断当天是否需要执行
        LocalDate today = LocalDate.now();
        if (!shouldExecuteToday(today, config)) {
            log.info("[{} 客户端定时任务] 当天无需执行", clientName);
            return;
        }

        // 执行时间（时分秒）
        LocalTime executionTime = calculateExecutionTime(config, clientName);
        if (executionTime == null) {
            return;
        }
        // 计划执行时间（年月日 时分秒）
        LocalDateTime executionDateTime = LocalDateTime.of(today, executionTime);
        // 计划执行时间晚于当前时间，则立即执行（在签到时间范围内重启服务的场景）
        if (executionDateTime.isBefore(LocalDateTime.now())) {
            log.info("[{} 客户端定时任务] 计划执行时间晚于当前时间,立即执行任务", clientName);
            signWebSocketHandler.executeSignIn(client);
            return;
        }

        // 创建延时执行任务
        ScheduledFuture<?> scheduledFuture = taskScheduler.schedule(
                () -> {
                    signWebSocketHandler.executeSignIn(client);
                    signClientService.updateAutoSignTime(clientName, null);
                },
                executionDateTime.atZone(ZoneId.systemDefault()).toInstant()
        );
        Map<String, Object> clientTaskInfo = new HashMap<>(2);
        clientTaskInfo.put("config", client.getConfig());
        clientTaskInfo.put("task", scheduledFuture);
        CLIENT_SCHEDULED_TASK_LIST.put(clientName, clientTaskInfo);
        log.info("[{} 客户端定时任务] 创建定时执行任务成功", clientName);
        signClientService.updateAutoSignTime(clientName, executionDateTime);
    }

    /**
     * 取消客户端定时任务
     *
     * @param clientName 客户端名称
     */
    public void cancelClientTask(String clientName) {
        Map<String, Object> map = CLIENT_SCHEDULED_TASK_LIST.remove(clientName);
        if (map == null) {
            return;
        }
        ScheduledFuture<?> task = (ScheduledFuture<?>) map.get("task");
        if (task.isDone()) {
            return;
        }
        // 取消任务
        task.cancel(true);
        log.info("[{} 客户端定时任务] 取消旧的客户端定时任务", clientName);
        signClientService.updateAutoSignTime(clientName, null);
    }

    /**
     * 检查日期是否需要执行
     *
     * @param date   日期
     * @param config 客户端配置
     * @return boolean
     */
    private boolean shouldExecuteToday(LocalDate date, ClientConfigDto config) {
        String dateStr = date.format(DateTimeFormatter.ISO_DATE);
        // 检查忽略日期
        if (config.getIgnoreDateList().contains(dateStr)) {
            return false;
        }
        // 检查特殊日期
        if (config.getSpecialDateList().contains(dateStr)) {
            return true;
        }
        // 检查星期 Monday=1, Sunday=7
        return config.getDayRangeList().contains(date.getDayOfWeek().getValue());
    }

    /**
     * 计算执行时间
     *
     * @param config 客户端配置
     * @return LocalTime
     */
    private LocalTime calculateExecutionTime(ClientConfigDto config, String clientName) {
        if (StringUtils.isBlank(config.getLatestPoint())
                || config.getEstimateExecMin() == null
                || config.getEarlyMinute() == null) {
            log.error("[{} 客户端定时任务] 计划执行时间为空！", clientName);
            return null;
        }
        // 最晚执行时间
        LocalTime latestTime = LocalTime.parse(config.getLatestPoint());
        if (latestTime.isBefore(LocalTime.now())) {
            log.info("[{} 客户端定时任务] 当前时间晚于最晚执行时间，不创建任务", clientName);
            return null;
        }

        // 实际最晚执行时间（减去预估执行时间）
        LocalTime latestExecutionTime = latestTime.minusMinutes(config.getEstimateExecMin());
        // 最早执行时间
        LocalTime earliestExecutionTime = latestTime.minusMinutes(config.getEarlyMinute());

        long start = earliestExecutionTime.toSecondOfDay();
        long end = latestExecutionTime.toSecondOfDay();
        long randomTime = ThreadLocalRandom.current().nextLong(start, end + 1);
        return LocalTime.ofSecondOfDay(randomTime);
    }

}